﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Mvc.ApplicationModels;

public class ConsumesConstraintForFormFileParameterConventionTest
{
    [Fact]
    public void AddMultipartFormDataConsumesAttribute_NoOpsIfConsumesConstraintIsAlreadyPresent()
    {
        // Arrange
        var actionName = nameof(TestController.ActionWithConsumesAttribute);
        var action = GetActionModel(typeof(TestController), actionName);
        var convention = GetConvention();

        // Act
        ConsumesConstraintForFormFileParameterConvention.AddMultipartFormDataConsumesAttribute(action);

        // Assert
        var attribute = Assert.Single(action.Filters);
        var consumesAttribute = Assert.IsType<ConsumesAttribute>(attribute);
        Assert.Equal("application/json", Assert.Single(consumesAttribute.ContentTypes));
    }

    [Fact]
    public void AddMultipartFormDataConsumesAttribute_AddsConsumesAttribute_WhenActionHasFromFormFileParameter()
    {
        // Arrange
        var actionName = nameof(TestController.FormFileParameter);
        var action = GetActionModel(typeof(TestController), actionName);
        action.Parameters[0].BindingInfo = new BindingInfo
        {
            BindingSource = BindingSource.FormFile,
        };
        var convention = GetConvention();

        // Act
        ConsumesConstraintForFormFileParameterConvention.AddMultipartFormDataConsumesAttribute(action);

        // Assert
        var attribute = Assert.Single(action.Filters);
        var consumesAttribute = Assert.IsType<ConsumesAttribute>(attribute);
        Assert.Equal("multipart/form-data", Assert.Single(consumesAttribute.ContentTypes));
    }

    private ConsumesConstraintForFormFileParameterConvention GetConvention()
    {
        return new ConsumesConstraintForFormFileParameterConvention();
    }

    private static ApplicationModelProviderContext GetContext(
        Type type,
        IModelMetadataProvider modelMetadataProvider = null)
    {
        var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() });
        var mvcOptions = Options.Create(new MvcOptions());
        modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider();
        var convention = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider);
        convention.OnProvidersExecuting(context);

        return context;
    }

    private static ActionModel GetActionModel(Type controllerType, string actionName)
    {
        var context = GetContext(controllerType);
        var controller = Assert.Single(context.Result.Controllers);
        return Assert.Single(controller.Actions, m => m.ActionName == actionName);
    }

    private class TestController
    {
        [HttpPost("form-file")]
        public IActionResult FormFileParameter(IFormFile formFile) => null;

        [HttpPost("form-file-collection")]
        public IActionResult FormFileCollectionParameter(IFormFileCollection formFiles) => null;

        [HttpPost("form-file-sequence")]
        public IActionResult FormFileSequenceParameter(IFormFile[] formFiles) => null;

        [HttpPost]
        public IActionResult FromFormParameter([FromForm] string parameter) => null;

        [HttpPost]
        [Consumes("application/json")]
        public IActionResult ActionWithConsumesAttribute([FromForm] string parameter) => null;
    }
}
